package com.xuecheng.media.service.impl;

import com.alibaba.nacos.common.utils.MD5Utils;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.api.R;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;

import com.j256.simplemagic.ContentInfo;
import com.j256.simplemagic.ContentInfoUtil;
import com.xuecheng.base.execption.XueChengPlusException;
import com.xuecheng.base.model.PageParam;
import com.xuecheng.base.model.RestResponse;
import com.xuecheng.content.model.po.PageResult;
import com.xuecheng.media.mapper.MediaFilesMapper;
import com.xuecheng.media.mapper.MediaProcessMapper;
import com.xuecheng.media.model.dto.QueryMediaParamsDto;
import com.xuecheng.media.model.dto.UpLoadMediaFilesDto;
import com.xuecheng.media.model.dto.UpLoadMediaFilesParamDto;
import com.xuecheng.media.model.po.MediaFiles;
import com.xuecheng.media.model.po.MediaProcess;
import com.xuecheng.media.service.MediaFileService;
import io.minio.*;
import io.minio.errors.*;
import io.minio.messages.DeleteError;
import io.minio.messages.DeleteObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.compress.utils.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.*;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @description TODO
 * @author Mr.M
 * @date 2022/9/10 8:58
 * @version 1.0
 */
 @Service
 @Slf4j
public class MediaFileServiceImpl implements MediaFileService {

 @Autowired
 MediaFilesMapper mediaFilesMapper;
 @Autowired
 MediaFileService mediaFileService;
 @Autowired
 MediaProcessMapper mediaProcessMapper;

 @Override
 public PageResult<MediaFiles> queryMediaFiels(Long companyId, PageParam pageParams, QueryMediaParamsDto queryMediaParamsDto) {

  //构建查询条件对象
  LambdaQueryWrapper<MediaFiles> queryWrapper = new LambdaQueryWrapper<>();
  queryWrapper.like(MediaFiles::getFilename, queryMediaParamsDto.getFilename());
  //分页对象
  if (pageParams.getPageSize()==0 ||pageParams.getPageSize()==0 ){
   pageParams.setPageSize(10);
  }
  Page<MediaFiles> page = new Page<>(pageParams.getPageNo(), pageParams.getPageSize());
  // 查询数据内容获得结果
  Page<MediaFiles> pageResult = mediaFilesMapper.selectPage(page, queryWrapper);
  // 获取数据列表

  List<MediaFiles> list = pageResult.getRecords();



  // 获取数据总数
  long total = pageResult.getTotal();
  // 构建结果集
  PageResult<MediaFiles> mediaListResult = new PageResult<>(list, total, pageParams.getPageNo(), pageParams.getPageSize());
  return mediaListResult;

 }

 /**
  * 上传媒资到minio以及数据库
  *
  * @param companyid 公司id
  * @param paramDto 请求的参数（即文件的信息）
  * @param localFilePath 文件保存路径
  * @return
  */
 @Value("${minio.bucket.files}")
 String bucket_files;
//上传文件到minio以及数据库
 @Override
 public UpLoadMediaFilesDto uploadfiles(Long companyid, UpLoadMediaFilesParamDto paramDto, String localFilePath,String objectname) {
  //创建一个文件
  File file = new File(localFilePath);
  if (!(file.exists())) {
   throw new XueChengPlusException("文件不存在");
  }
  //获得文件的拓展名
  String filename = paramDto.getFilename();
  String extension = filename.substring(filename.lastIndexOf("."));
  String mimeType = getmimeType(extension);
  //获取文件默认存储目录路径 年/月/日
  String defaultFolderPath = getDefaultFolderPath();
  //获得文件的md5值
  String fileMd5 = getFileMd5(file);
  //如果传参objectname为空，就用默认的年月日路径

  if (StringUtils.isEmpty(objectname)) {
   objectname = defaultFolderPath + fileMd5 + extension;
  }
  //不为空的话就用传参的objectname

  //上传到minio
  Boolean aBoolean = uploadFiletominio(bucket_files, localFilePath, objectname, mimeType);
  if (aBoolean == false) {
   throw new XueChengPlusException("上传文件到minio失败");
  }
  MediaFiles mediaFiles = mediaFileService.addfilesToDb(companyid, paramDto, fileMd5, bucket_files, objectname);
  if (mediaFiles == null) {
   throw new XueChengPlusException("保存信息失败");
  }
  //填充信息到返回对象
  UpLoadMediaFilesDto upLoadMediaFilesDto = new UpLoadMediaFilesDto();
  BeanUtils.copyProperties(mediaFiles, upLoadMediaFilesDto);
  return upLoadMediaFilesDto;
 }

 //将数据保存到数据库

 //获得拓展名
 public String getmimeType(String extension) {
  if(extension==null)
   extension = "";
  //根据扩展名取出mimeType
  ContentInfo extensionMatch = ContentInfoUtil.findExtensionMatch(extension);
  //通用mimeType，字节流
  String mimeType = MediaType.APPLICATION_OCTET_STREAM_VALUE;
  if(extensionMatch!=null){
   mimeType = extensionMatch.getMimeType();
  }
  return mimeType;
 }

// @Value("${minio.endpoint}")
// private String endpoint;
// @Value("${minio.accessKey}")
// private String accessKey;
// @Value("${minio.secretKey}")
// private String secretKey;

 //上传文件到minio
 public Boolean uploadFiletominio(String bucket, String localFilePath, String object, String mimeType) {

  try {
   UploadObjectArgs build = UploadObjectArgs.builder().bucket(bucket).filename(localFilePath).object(object).contentType(mimeType).build();
   minioClient.uploadObject(build);
   log.debug("上传文件到minio成功,bucket:{},objectName:{}", bucket, object);
   System.out.println("上传成功");
   return true;
  } catch (Exception e) {
   e.printStackTrace();
   log.error("上传文件到minio出错,bucket:{},objectName:{},错误原因:{}", bucket, object, e.getMessage(), e);
   XueChengPlusException.cast("上传文件到文件系统失败");
   return false;
  }

 }

 //获取文件默认存储目录路径 年/月/日
 private String getDefaultFolderPath() {
  SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
  String folder = sdf.format(new Date()).replace("-", "/") + "/";
  return folder;
 }

 //获取文件的md5值
 public String getFileMd5(File file) {
  try {
   FileInputStream inputStream = new FileInputStream(file);
   String s = DigestUtils.md5Hex(inputStream);
   return s;
  } catch (FileNotFoundException e) {
   e.printStackTrace();
   return null;
  } catch (IOException e) {
   e.printStackTrace();
   return null;
  }

 }

 /**
  * 添加待处理任务
  * @param mediaFiles
  */
 public void addWaitingTask(MediaFiles mediaFiles){
    //先判断该文件是否是avi文件，如果是的话就添加到待处理任务表中===================
  String filename = mediaFiles.getFilename();
  //拿到扩展名
  String extension = filename.substring(filename.lastIndexOf("."));
  String mimeType = getmimeType(extension);
  //判断该文件拓展名是否是avi格式,如果是的话就插入到数据库=============================
  if(mimeType.equals("video/x-msvideo")){
   MediaProcess mediaProcess = new MediaProcess();
   BeanUtils.copyProperties(mediaFiles,mediaProcess);

   mediaProcess.setStatus("1");
   mediaProcess.setFailCount(0);
   mediaProcess.setUrl(null);
   mediaProcessMapper.insert(mediaProcess);

  }
 }

 /**
  * 上传到数据库
  *
  * @param companyid
  * @param paramDto
  * @param filedm5
  * @param bucket
  * @param objectname
  * @return
  */
 @Transactional
 public MediaFiles addfilesToDb(Long companyid, UpLoadMediaFilesParamDto paramDto,
                                String filedm5, String bucket, String objectname) {
  //从数据库查询
  MediaFiles mediaFiles0 = mediaFilesMapper.selectById(filedm5);
  MediaFiles mediaFiles1 = null;
  //如果id不存在就创建一个
  if (mediaFiles0 == null) {
   mediaFiles1 = new MediaFiles();
   //将信息拷贝到对象
   BeanUtils.copyProperties(paramDto, mediaFiles1);
   mediaFiles1.setId(filedm5);
   mediaFiles1.setFileId(filedm5);
   mediaFiles1.setCompanyId(companyid);
   mediaFiles1.setBucket(bucket);
   mediaFiles1.setUrl("/" + bucket + "/" + objectname);
   mediaFiles1.setFilePath(objectname);
   mediaFiles1.setCreateDate(LocalDateTime.now());
   mediaFiles1.setAuditStatus("002003");
   mediaFiles1.setFileSize(paramDto.getFileSize());
   mediaFiles1.setStatus("1");


  }
//     int i =1/0;
//加入到数据库中
  int insert = mediaFilesMapper.insert(mediaFiles1);
  if (insert > 0) {
   System.out.println("上传到media数据库成功");
   log.debug("保存文件信息到mediafiles数据库成功,{}", mediaFiles1.toString());
   //添加到待处理任务表===========
   addWaitingTask(mediaFiles1);
   log.debug("保存文件信息到mediaprocess数据库成功,{}", mediaFiles1.toString());
  } else {
   log.error("保存文件信息到数据库失败,{}", mediaFiles1.toString());
//   throw new XueChengPlusException("上传到media数据库失败");
  }

  return mediaFiles1;
 }

 @Autowired
 MinioClient minioClient;
 @Value("${minio.bucket.videofiles}")
 String video_bucket;

 //上传文件前的检查
 @Override
 public RestResponse<Boolean> checkfiles(String fileMd5) {

  //检查数据库是否存在
  MediaFiles mediaFiles = mediaFilesMapper.selectById(fileMd5);
  //如果数据库存在数据，查看minio是否存在
  if (mediaFiles != null) {
   GetObjectArgs build = GetObjectArgs.builder().object(mediaFiles.getFilePath()).bucket(video_bucket).build();
   try {
    FilterInputStream inputStream = minioClient.getObject(build);
    if (inputStream != null) {
     //文件已经存在

     ;
     return RestResponse.success(true);
    }

   } catch (Exception e) {
    e.printStackTrace();

   }
   //上述都跳过的话，证明文件不存在



  }return RestResponse.success(false);}



/**
 * 检查单个分块文件是否存在,注意该处的fileMd5是源文件的md5
 */
@Override
public RestResponse<Boolean> checkchunkfiles(String fileMd5, int chunkindex) {
 String chunkFileFolderPath = getChunkFileFolderPath(fileMd5);
 String objectname = chunkFileFolderPath + chunkindex;
 GetObjectArgs build = GetObjectArgs.builder().object(objectname).bucket(video_bucket).build();
 try {
  FilterInputStream filterInputStream = minioClient.getObject(build);
  if (filterInputStream!=null){
   //该文件存在
   return RestResponse.success(true);
  }
 } catch (Exception e) {
  e.printStackTrace();
 }
 //该文件不存在
 return RestResponse.success(false);
}

//得到分块文件的目录

public String getChunkFileFolderPath(String fileMd5) {
 return fileMd5.substring(0, 1) + "/" + fileMd5.substring(1, 2) + "/" + fileMd5 + "/" + "chunk" + "/";
}
//得到合并文件的目录

 public String getMergeFileFolderPath(String fileMd5,String extension) {
  return fileMd5.substring(0, 1) + "/" + fileMd5.substring(1, 2) + "/" + fileMd5 + "/" +  fileMd5 + extension;
 }
//上传分块文件到minio
 public RestResponse<Boolean> uploadChunk(String fileMd5, int chunkindex,String LocalChunkPath){
 String objectname = getChunkFileFolderPath(fileMd5) + chunkindex;
  String s = getmimeType("");
  Boolean aBoolean = uploadFiletominio(video_bucket, LocalChunkPath, objectname, s);
  if (aBoolean){
   log.debug("上传分块文件到minio成功，filemd5：{},chunkindex:{}",fileMd5,chunkindex);
  return RestResponse.success(true);
  }
  log.debug("上传分块文件失败:{}", LocalChunkPath);
 return RestResponse.validfail(false,"上传分块文件失败");

 }

 //合并分块文件并且上传到minio和数据库
 @Override
 public RestResponse<Boolean> mergechunks(UpLoadMediaFilesParamDto upLoadMediaFilesParamDto, Long companyid, String filemd5, int chunktotal) {
  //1.合并分块文件
  ArrayList<ComposeSource> composeSources = new ArrayList<>();
  //遍历将分块文件加入到集合当中
  for (int i = 0; i < chunktotal; i++) {
   //获得文件名
   String objectname = getChunkFileFolderPath(filemd5 )+ Integer.toString(i);

   ComposeSource build = ComposeSource.builder().bucket(video_bucket).object(objectname).build();
   composeSources.add(build);
  }
  //开始将分块文件合并到一个文件，并上传到minio
  String filename = upLoadMediaFilesParamDto.getFilename();
  String extension = filename.substring(filename.lastIndexOf("."));
  //合并文件的路径
  String mergeFileFolderPath = getMergeFileFolderPath(filemd5,extension);
  ComposeObjectArgs build = ComposeObjectArgs.
          builder().
          sources(composeSources).
          bucket(video_bucket).
          object(mergeFileFolderPath).
          build();
  try {

   ObjectWriteResponse objectWriteResponse = minioClient.composeObject(build);
   log.error("合并上传文件成功，filemd5:{}",filemd5);

  } catch (Exception e) {
   e.printStackTrace();

//   return RestResponse.validfail(false, "下载合并后文件失败。");
   throw new XueChengPlusException("下载合并后文件失败");
  }
//2.校验合并文件的md5和源文件是否一致，一致则成功，否则失败
  //下载minio的源文件
  File file = downloadFileFromMinIO(video_bucket, mergeFileFolderPath);

  try {
   String downloadfilemd5 = DigestUtils.md5Hex(new FileInputStream(file));
   if (downloadfilemd5.equals(filemd5)){
      log.debug("合并上传后文件完整");
    upLoadMediaFilesParamDto.setFileSize(file.length());
   }
   else {
    return RestResponse.validfail(false, "文件合并校验失败，最终上传失败。");
   }

  } catch (IOException e) {
   e.printStackTrace();
   log.debug("校验文件失败,fileMd5:{},异常:{}",filemd5,e.getMessage(),e);

//   return RestResponse.validfail(false, "文件合并校验失败，最终上传失败。");
   throw new XueChengPlusException("校验文件失败");
  }
  finally {//最后删除下载的文件
   if (file!=null){
    file.delete();
   }
  }
  //3.将文件入库
  MediaFiles mediaFiles = mediaFileService.addfilesToDb(companyid, upLoadMediaFilesParamDto, filemd5, video_bucket, mergeFileFolderPath);

  if (mediaFiles==null){
   log.debug("文件存入数据失败,mediaFiles:{}",mediaFiles);
   return RestResponse.validfail(false, "文件存入数据失败");

  }
else {
   log.debug("文件存入数据库成功,mediaFiles:{}",mediaFiles);
  }
//4.清理分块，封装一个方法

   String chunkFileFolderPath = getChunkFileFolderPath(filemd5);

   clearChunkFiles(chunkFileFolderPath,chunktotal);

return RestResponse.success(true);
 }
 /**
  * 从minio下载文件
  * @param bucket 桶
  * @param objectName 对象名称
  * @return 下载后的文件
  */
 public File downloadFileFromMinIO(String bucket,String objectName){
  //临时文件
  File minioFile = null;
  FileOutputStream outputStream = null;
  try{
   InputStream stream = minioClient.getObject(GetObjectArgs.builder()
           .bucket(bucket)
           .object(objectName)
           .build());
   //创建临时文件
   minioFile=File.createTempFile("minio", ".merge");
   outputStream = new FileOutputStream(minioFile);
   IOUtils.copy(stream,outputStream);
   return minioFile;
  } catch (Exception e) {
   e.printStackTrace();
  }finally {
   if(outputStream!=null){
    try {
     outputStream.close();
    } catch (IOException e) {
     e.printStackTrace();
    }
   }
  }
  return null;
 }
 /**
  * 清除分块文件
  * @param chunkFileFolderPath 分块文件路径
  * @param chunkTotal 分块文件总数
  */
 private void clearChunkFiles(String chunkFileFolderPath,int chunkTotal){

  try {
   List<DeleteObject> deleteObjects = Stream.iterate(0, i -> ++i)
           .limit(chunkTotal)
           .map(i -> new DeleteObject(chunkFileFolderPath.concat(Integer.toString(i))))
           .collect(Collectors.toList());

   RemoveObjectsArgs removeObjectsArgs = RemoveObjectsArgs.builder().bucket("video").objects(deleteObjects).build();
   Iterable<Result<DeleteError>> results = minioClient.removeObjects(removeObjectsArgs);
   results.forEach(r->{
    DeleteError deleteError = null;
    try {
     deleteError = r.get();
    } catch (Exception e) {
     e.printStackTrace();
     log.error("清楚分块文件失败,objectname:{}",deleteError.objectName(),e);
    }
   });
  } catch (Exception e) {
   e.printStackTrace();
   log.error("清楚分块文件失败,chunkFileFolderPath:{}",chunkFileFolderPath,e);
  }

}}